<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.0.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/icon.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/ficon.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/icon.png">
  <link rel="mask-icon" href="/images/icon.png" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib/pace/pace-theme-minimal.min.css">
  <script src="/lib/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"bqlin.github.io","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":true,"comments":{"style":"tabs","active":"gitalk","storage":true,"lazyload":false,"nav":{"gitalk":{"order":-2}},"activeClass":"gitalk"},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":-1,"unescape":false,"preload":false},"motion":{"enable":false,"async":true,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="当你使用音频队列服务进行录制的时候，你可以将音频录制到任何地方：磁盘文件、网络连接或内存对象等等。本章将介绍中最常见的一种情况，将音频录制到磁盘文件中。">
<meta property="og:type" content="article">
<meta property="og:title" content="Audio Queue Services Programming Guide：录制音频">
<meta property="og:url" content="https://bqlin.github.io/posts/audio_queue_services_pg_recording_audio/index.html">
<meta property="og:site_name" content="權咚领域">
<meta property="og:description" content="当你使用音频队列服务进行录制的时候，你可以将音频录制到任何地方：磁盘文件、网络连接或内存对象等等。本章将介绍中最常见的一种情况，将音频录制到磁盘文件中。">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2021-09-27T04:17:11.000Z">
<meta property="article:modified_time" content="2021-09-27T04:17:11.000Z">
<meta property="article:author" content="權咚">
<meta property="article:tag" content="Apple">
<meta property="article:tag" content="Audio Queue Services Programming Guide">
<meta property="article:tag" content="音视频">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="https://bqlin.github.io/posts/audio_queue_services_pg_recording_audio/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>Audio Queue Services Programming Guide：录制音频 | 權咚领域</title>
  
    <script async src="https://www.googletagmanager.com/gtag/js?id=UA-105338942-1"></script>
    <script>
      if (CONFIG.hostname === location.hostname) {
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', 'UA-105338942-1');
      }
    </script>


  <script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?98fcf61fd269408d07dcbc1e49311f9a";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>




  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/atom.xml" title="權咚领域" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">權咚领域</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">Making app a work of art.</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://bqlin.github.io/posts/audio_queue_services_pg_recording_audio/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/icon.png">
      <meta itemprop="name" content="權咚">
      <meta itemprop="description" content="🤘🤯👾">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="權咚领域">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          Audio Queue Services Programming Guide：录制音频
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2021-09-27 12:17:11" itemprop="dateCreated datePublished" datetime="2021-09-27T12:17:11+08:00">2021-09-27</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E7%BF%BB%E8%AF%91/" itemprop="url" rel="index"><span itemprop="name">翻译</span></a>
                </span>
            </span>

          
            <span class="post-meta-item" title="阅读次数" id="busuanzi_container_page_pv" style="display: none;">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">阅读次数：</span>
              <span id="busuanzi_value_page_pv"></span>
            </span><br>
            <span class="post-meta-item" title="本文字数">
              <span class="post-meta-item-icon">
                <i class="far fa-file-word"></i>
              </span>
                <span class="post-meta-item-text">本文字数：</span>
              <span>13k</span>
            </span>
            <span class="post-meta-item" title="阅读时长">
              <span class="post-meta-item-icon">
                <i class="far fa-clock"></i>
              </span>
                <span class="post-meta-item-text">阅读时长 &asymp;</span>
              <span>12 分钟</span>
            </span>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p>当你使用音频队列服务进行录制的时候，你可以将音频录制到任何地方：磁盘文件、网络连接或内存对象等等。本章将介绍中最常见的一种情况，将音频录制到磁盘文件中。</p>
<span id="more"></span>
<blockquote>
<p><strong>注意：</strong>本章介绍了基于ANSI-C的录制的实现，并且使用了MAC OS X中Core Audio SDK中了一些C++类，如果想了解基于Objective-C的例子，请参考<a target="_blank" rel="noopener" href="http://developer.apple.com/devcenter/ios/">iOS Dev Center</a>中的_SpeakHere_例子。</p>
</blockquote>
<p>要把录制功能添加到程序中，一般都要进行以下几个步骤：</p>
<ol type="1">
<li>定义一个自定义的结构体来管理状态、格式以及路径信息等。</li>
<li>编写音频队列回调函数来执行实际的录制工作。</li>
<li>（可选）编写代码来为音频队列缓冲区选择一个合适的大小。如果你将要录制的格式使用了magic cookies，你需要编写相应的代码来配合使用。</li>
<li>填充自定义结构体中的各个字段，包括指定音频队列将要录制到的文件的数据流、文件路径。</li>
<li>创建一个用于录制的音频队列并且让音频队列创建一系列的音频队列缓冲区，同时创建一个将要写入的文件。</li>
<li>通知音频队列开始录制。</li>
<li>录制完毕之后，通知音频队列停止录制，然后释放掉它，同时它会释放掉它所拥有的缓冲区。</li>
</ol>
<p>本章的剩余部分将详细描述上述的每一个步骤。</p>
<h2 id="定义一个管理状态的结构体">定义一个管理状态的结构体</h2>
<p>使用音频队列服务来开发一个音频录制解决方案的时候，第一步就是定义一个结构体。将使用这个结构体来管理音频格式和音频队列状态信息。清单2-1展示了这个这样的一个结构体。</p>
<p><strong>清单2-1</strong> 一个用于录制的音频队列的结构体</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> kNumberBuffers = <span class="number">3</span>;                            <span class="comment">// 1</span></span><br><span class="line"><span class="keyword">struct</span> AQRecorderState &#123;</span><br><span class="line">    AudioStreamBasicDescription  mDataFormat;                   <span class="comment">// 2</span></span><br><span class="line">    AudioQueueRef                mQueue;                        <span class="comment">// 3</span></span><br><span class="line">    AudioQueueBufferRef          mBuffers[kNumberBuffers];      <span class="comment">// 4</span></span><br><span class="line">    AudioFileID                  mAudioFile;                    <span class="comment">// 5</span></span><br><span class="line">    <span class="built_in">UInt32</span>                       bufferByteSize;                <span class="comment">// 6</span></span><br><span class="line">    SInt64                       mCurrentPacket;                <span class="comment">// 7</span></span><br><span class="line">    <span class="keyword">bool</span>                         mIsRunning;                    <span class="comment">// 8</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>下面是这个结构体中每个字段的说明：</p>
<ol type="1">
<li>要使用的音频队列缓冲区的数量。</li>
<li>一个<code>AudioStreamBasicDescription</code>结构体（来自<code>CoreAudioTypes.h</code>），表示将要写入磁盘的音频数据的格式，音频队列缓冲区使用这个格式来指定它的<code>mQueue</code>字段。<code>mDataFormat</code>字段是由你的程序初始化的，参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW4">Set Up an Audio Format for Recording</a>。可以通过查询音频队列的<code>kAudioQueueProperty_StreamDescription</code>属性来更新这个字段的值，参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW23">Getting the Full Audio Format from an Audio Queue</a>。在Mac OS X v10.5中要使用<code>kAudioConverterCurrentInputStreamDescription</code>。 关于<code>AudioStreamBasicDescription</code>结构体的详细信息，请参阅_<a target="_blank" rel="noopener" href="https://developer.apple.com/documentation/coreaudio/core_audio_data_types">Core Audio Data Types Reference</a>_。</li>
<li>由程序创建的音频队列。</li>
<li>一个指向由音频队列所管理的音频队列缓冲区的指针数组。</li>
<li>一个表示程序录制音频时写入的文件的音频文件对象。</li>
<li>每个音频队列缓冲区的字节大小。它的值在随后的例子中的<code>DeriveBufferSize</code>函数中计算出来，它在音频队列创建后，开始录制音频前计算出来，参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW14">Write a Function to Derive Recording Audio Queue Buffer Size</a>。</li>
<li>从当前音频队列缓冲区写入文件的第一个包（packet）的索引。</li>
<li>一个布尔值，表示音频队列是否在运行中。</li>
</ol>
<h2 id="编写用于录制的音频队列的回调函数">编写用于录制的音频队列的回调函数</h2>
<p>接下来，编写一个用于录制的回调函数，这个函数主要做两个事情：</p>
<ul>
<li>将新填充进音频队列缓冲区的内容写入你正在录制的文件中。</li>
<li>将刚才已经将内容写入文件的音频队列缓冲区入队到缓冲区队列。</li>
</ul>
<p>下面展示了一个回调函数声明的列子，然后分别描述这两个任务，最后展示一个完整的用于录制的回调函数。关于用于录制的音频队列回调函数所扮演的角色，可以参考<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AboutAudioQueues/AboutAudioQueues.html#//apple_ref/doc/uid/TP40005343-CH5-SW2">图1-3</a>。</p>
<h3 id="用于录制的音频队列回调函数的声明">用于录制的音频队列回调函数的声明</h3>
<p>清单2-2是一个用于录制的音频队列回调函数声明，是在<code>AudioQueue.h</code>中声明的<code>AudioQueueInputCallback</code>：</p>
<p><strong>清单2-2</strong>  用于录制的音频队列回调函数声明</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> HandleInputBuffer (</span><br><span class="line">    <span class="keyword">void</span>                                *aqData,             <span class="comment">// 1</span></span><br><span class="line">    AudioQueueRef                       inAQ,                <span class="comment">// 2</span></span><br><span class="line">    AudioQueueBufferRef                 inBuffer,            <span class="comment">// 3</span></span><br><span class="line">    <span class="keyword">const</span> AudioTimeStamp                *inStartTime,        <span class="comment">// 4</span></span><br><span class="line">    <span class="built_in">UInt32</span>                              inNumPackets,        <span class="comment">// 5</span></span><br><span class="line">    <span class="keyword">const</span> AudioStreamPacketDescription  *inPacketDesc        <span class="comment">// 6</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>一般来说，<code>aqData</code>是一个自定义的数据结构，包含了音频队列的状态信息，参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW15">Define a Custom Structure to Manage State</a>。</li>
<li>拥有该回调函数的音频队列。</li>
<li>包含录制数据的音频队列缓冲区。</li>
<li>音频队列缓冲区中第一个采样的时间（对于简单的录制是不需要的）。</li>
<li><code>inPacketDesc</code>字段中包描述的数量，如果是0，表明这是个CBR数据。</li>
<li>对于压缩数据格式如果需要包描述，包描述是由编码器产生的。</li>
</ol>
<h3 id="将音频队列缓冲区中的数据写入磁盘">将音频队列缓冲区中的数据写入磁盘</h3>
<p>用于录制的音频队列回调函数要做的第一件事情就是把音频队列缓冲区中的内容写入磁盘。这个缓冲区就是音频队列从输入设备最新输入的音频数据。这个回调函数使用<code>AudioFile.h</code>中声明的<code>AudioFileWritePackets</code>函数。如清单2-3所示。</p>
<p><strong>清单2-3</strong> 将音频队列缓冲区数据写入磁盘</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">AudioFileWritePackets (                     <span class="comment">// 1</span></span><br><span class="line">    pAqData-&gt;mAudioFile,                    <span class="comment">// 2</span></span><br><span class="line">    <span class="literal">false</span>,                                  <span class="comment">// 3</span></span><br><span class="line">    inBuffer-&gt;mAudioDataByteSize,           <span class="comment">// 4</span></span><br><span class="line">    inPacketDesc,                           <span class="comment">// 5</span></span><br><span class="line">    pAqData-&gt;mCurrentPacket,                <span class="comment">// 6</span></span><br><span class="line">    &amp;inNumPackets,                          <span class="comment">// 7</span></span><br><span class="line">    inBuffer-&gt;mAudioData                    <span class="comment">// 8</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>AudioFileWritePackets</code>函数（在<code>AudioFile.h</code>声明），把缓冲区的内容写入音频数据文件中。</li>
<li>音频文件对象（类型为<code>AudioFileID</code>）表示要写到的音频文件。<code>pAqData</code>变量是指向<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW5">清单2-1</a>描述的数据结构的指针。</li>
<li>使用<code>false</code>值来表达写入是函数不应缓存数据。</li>
<li>正在写入的音频数据的字节数。<code>inBuffer</code>变量音频队列传递给回调函数的音频队列缓冲区。</li>
<li>音频数据包描述数组。<code>NULL</code>值表示不需要数据包描述（例如，CBR音频数据）。</li>
<li>要写入的第一个数据包的索引。</li>
<li>输入时，表示要写入的数据包数量。输出时，表示实际写入的数据包数量。</li>
<li>将新的音频数据写入音频文件。</li>
</ol>
<h3 id="排队音频队列缓冲区">排队音频队列缓冲区</h3>
<p>现在，音频队列缓冲区的音频数据已经被写入音频文件，回调对缓冲区进行排队，如清单2-4所示。一旦回到缓冲区队列中，缓冲区就处于排队状态，准备接受更多传入的音频数据。</p>
<p><strong>清单2-4</strong> 写入磁盘后排队音频队列缓冲区</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">AudioQueueEnqueueBuffer (                    <span class="comment">// 1</span></span><br><span class="line">    pAqData-&gt;mQueue,                         <span class="comment">// 2</span></span><br><span class="line">    inBuffer,                                <span class="comment">// 3</span></span><br><span class="line">    <span class="number">0</span>,                                       <span class="comment">// 4</span></span><br><span class="line">    <span class="literal">NULL</span>                                     <span class="comment">// 5</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>AudioQueueEnqueueBuffer</code>函数把音频队列缓冲区添加到音频队列的缓冲区队列中。</li>
<li>把指定的音频队列缓冲区添加到音频队列。<code>pAqData</code>变量指向清单2-1描述的数据结构指针。</li>
<li>要排队的音频队列缓冲区。</li>
<li>音频队列缓冲区数据中的数据包描述数量。设为<code>0</code>，因为该参数未用于录制。</li>
<li>数据包描述数组，描述音频队列缓冲区的数据。设为<code>NULL</code>，因为该参数未用于录制。</li>
</ol>
<h3 id="一个完整的音频录制的音频队列回调函数">一个完整的音频录制的音频队列回调函数</h3>
<p>清单2-5展示了完整的音频录制中音频队列回调函数的基本形式。与本文档的其他代码一样，该清单不包括错误处理。</p>
<p><strong>清单2-5</strong> 一个音频录制的音频队列回调函数</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> HandleInputBuffer (</span><br><span class="line">    <span class="keyword">void</span>                                 *aqData,</span><br><span class="line">    AudioQueueRef                        inAQ,</span><br><span class="line">    AudioQueueBufferRef                  inBuffer,</span><br><span class="line">    <span class="keyword">const</span> AudioTimeStamp                 *inStartTime,</span><br><span class="line">    <span class="built_in">UInt32</span>                               inNumPackets,</span><br><span class="line">    <span class="keyword">const</span> AudioStreamPacketDescription   *inPacketDesc</span><br><span class="line">) &#123;</span><br><span class="line">    AQRecorderState *pAqData = (AQRecorderState *) aqData;               <span class="comment">// 1</span></span><br><span class="line"> </span><br><span class="line">    <span class="keyword">if</span> (inNumPackets == <span class="number">0</span> &amp;&amp;                                             <span class="comment">// 2</span></span><br><span class="line">          pAqData-&gt;mDataFormat.mBytesPerPacket != <span class="number">0</span>)</span><br><span class="line">       inNumPackets =</span><br><span class="line">           inBuffer-&gt;mAudioDataByteSize / pAqData-&gt;mDataFormat.mBytesPerPacket;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">if</span> (AudioFileWritePackets (                                          <span class="comment">// 3</span></span><br><span class="line">            pAqData-&gt;mAudioFile,</span><br><span class="line">            <span class="literal">false</span>,</span><br><span class="line">            inBuffer-&gt;mAudioDataByteSize,</span><br><span class="line">            inPacketDesc,</span><br><span class="line">            pAqData-&gt;mCurrentPacket,</span><br><span class="line">            &amp;inNumPackets,</span><br><span class="line">            inBuffer-&gt;mAudioData</span><br><span class="line">        ) == noErr) &#123;</span><br><span class="line">            pAqData-&gt;mCurrentPacket += inNumPackets;                     <span class="comment">// 4</span></span><br><span class="line">    &#125;</span><br><span class="line">   <span class="keyword">if</span> (pAqData-&gt;mIsRunning == <span class="number">0</span>)                                         <span class="comment">// 5</span></span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line"> </span><br><span class="line">    AudioQueueEnqueueBuffer (                                            <span class="comment">// 6</span></span><br><span class="line">        pAqData-&gt;mQueue,</span><br><span class="line">        inBuffer,</span><br><span class="line">        <span class="number">0</span>,</span><br><span class="line">        <span class="literal">NULL</span></span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>实例化时提供给音频队列对象的结构体，包含代表要记录到其中的音频文件的对象，以及各种状态数据。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW15">Define a Custom Structure to Manage State</a>。</li>
<li>如果音频队列缓冲区包含CBR数据，则需要计算缓冲区的数据包数量。该数值等于缓冲区中数据的总字节除以每个数据包固定的字节数。对于VBR数据，音频队列在调用回调时会提供缓冲区中的数据包数量。</li>
<li>把缓冲区的内容写入到音频数据文件中。有关详细的说明，参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW3">Writing an Audio Queue Buffer to Disk</a>。</li>
<li>如果成功写入音频数据，需要增加音频数据文件的数据包索引，以准备写入下一个缓冲区的音频数据。</li>
<li>如果音频队列已停止，则返回。</li>
<li>入队该写入音频文件的音频队列缓冲区。有关详细的说明，参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW8">Enqueuing an Audio Queue Buffer</a>。</li>
</ol>
<h2 id="编写函数计算用于录制的音频队列缓冲区大小">编写函数计算用于录制的音频队列缓冲区大小</h2>
<p>音频队列服务希望程序为使用的音频队列缓冲区指定大小。清单2-6展示了一种执行该操作的方法。它得出的缓冲区大小足以容纳给定的音频时长。</p>
<p>该计算考虑了要录制到的音频数据格式。该格式包含可能影响缓冲区大小的所有因素，例如音频通道的数量。</p>
<p><strong>清单2-6</strong> 得出音频录制的音频队列缓冲区的大小</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> DeriveBufferSize (</span><br><span class="line">    AudioQueueRef                audioQueue,                  <span class="comment">// 1</span></span><br><span class="line">    AudioStreamBasicDescription  &amp;ASBDescription,             <span class="comment">// 2</span></span><br><span class="line">    Float64                      seconds,                     <span class="comment">// 3</span></span><br><span class="line">    <span class="built_in">UInt32</span>                       *outBufferSize               <span class="comment">// 4</span></span><br><span class="line">) &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">int</span> maxBufferSize = <span class="number">0x50000</span>;                 <span class="comment">// 5</span></span><br><span class="line"> </span><br><span class="line">    <span class="keyword">int</span> maxPacketSize = ASBDescription.mBytesPerPacket;       <span class="comment">// 6</span></span><br><span class="line">    <span class="keyword">if</span> (maxPacketSize == <span class="number">0</span>) &#123;                                 <span class="comment">// 7</span></span><br><span class="line">        <span class="built_in">UInt32</span> maxVBRPacketSize = <span class="keyword">sizeof</span>(maxPacketSize);</span><br><span class="line">        AudioQueueGetProperty (</span><br><span class="line">                audioQueue,</span><br><span class="line">                kAudioQueueProperty_MaximumOutputPacketSize,</span><br><span class="line">                <span class="comment">// in Mac OS X v10.5, instead use</span></span><br><span class="line">                <span class="comment">//   kAudioConverterPropertyMaximumOutputPacketSize</span></span><br><span class="line">                &amp;maxPacketSize,</span><br><span class="line">                &amp;maxVBRPacketSize</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    Float64 numBytesForTime =</span><br><span class="line">        ASBDescription.mSampleRate * maxPacketSize * seconds; <span class="comment">// 8</span></span><br><span class="line">    *outBufferSize =</span><br><span class="line">    <span class="built_in">UInt32</span> (numBytesForTime &lt; maxBufferSize ?</span><br><span class="line">        numBytesForTime : maxBufferSize);                     <span class="comment">// 9</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>配置缓冲区大小的音频队列。</li>
<li>音频队列的<code>AudioStreamBasicDescription</code>结构体。</li>
<li>为每个音频队列缓冲区指定的大小（以秒为单位）。</li>
<li>在输出时，每个音频队列缓冲区的大小（以字节为单位）。</li>
<li>音频队列缓冲区大小上限（以字节为单位）。在该示例中，上限设为320 KB。这相当于以96 kHz的采样率采集5秒的24位立体声音频。</li>
<li>对于CBR音频数据，则从<code>AudioStreamBasicDescription</code>结构体中获取固定的数据包大小。使用该值作为最大数据包大小。 该赋值会有副作用，这取决于要录制的音频数据时CBR还是VBR。如果是VBR，则音频队列的<code>AudioStreamBasicDescription</code>会把bytes-per-packet设为<code>0</code>。</li>
<li>对于VBR音频数据，查询音频队列以获取最大的数据包估算大小。</li>
<li>得出缓冲区大小（以字节为单位）。</li>
<li>如果需要，把缓冲区大小限制为之前设置的上限。</li>
</ol>
<h2 id="设置音频文件magic-cookie">设置音频文件Magic Cookie</h2>
<p>某些压缩的音频格式（例如MPEG 4 AAC），利用结构体包含音频元数据。这些结构体称为<strong>magic cookies</strong>。使用音频队列服务以这种格式录制时，必须先从音频队列中获取magic cookie，然后再将其添加到音频文件中，然后开始录制。</p>
<p>清单2-7展示了如何从音频队列中获取magic cookie，并将其应用于音频文件中。代码会在录制之前调用该函数，然后在录制后再次调用，因为某些编解码器会在录制停止时更新magic cookie数据。</p>
<p><strong>清单2-7</strong> 给音频文件设置magic cookie</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">OSStatus SetMagicCookieForFile (</span><br><span class="line">    AudioQueueRef inQueue,                                      <span class="comment">// 1</span></span><br><span class="line">    AudioFileID   inFile                                        <span class="comment">// 2</span></span><br><span class="line">) &#123;</span><br><span class="line">    OSStatus result = noErr;                                    <span class="comment">// 3</span></span><br><span class="line">    <span class="built_in">UInt32</span> cookieSize;                                          <span class="comment">// 4</span></span><br><span class="line"> </span><br><span class="line">    <span class="keyword">if</span> (</span><br><span class="line">            AudioQueueGetPropertySize (                         <span class="comment">// 5</span></span><br><span class="line">                inQueue,</span><br><span class="line">                kAudioQueueProperty_MagicCookie,</span><br><span class="line">                &amp;cookieSize</span><br><span class="line">            ) == noErr</span><br><span class="line">    ) &#123;</span><br><span class="line">        <span class="keyword">char</span>* magicCookie =</span><br><span class="line">            (<span class="keyword">char</span> *) malloc (cookieSize);                       <span class="comment">// 6</span></span><br><span class="line">        <span class="keyword">if</span> (</span><br><span class="line">                AudioQueueGetProperty (                         <span class="comment">// 7</span></span><br><span class="line">                    inQueue,</span><br><span class="line">                    kAudioQueueProperty_MagicCookie,</span><br><span class="line">                    magicCookie,</span><br><span class="line">                    &amp;cookieSize</span><br><span class="line">                ) == noErr</span><br><span class="line">        )</span><br><span class="line">            result =    AudioFileSetProperty (                  <span class="comment">// 8</span></span><br><span class="line">                            inFile,</span><br><span class="line">                            kAudioFilePropertyMagicCookieData,</span><br><span class="line">                            cookieSize,</span><br><span class="line">                            magicCookie</span><br><span class="line">                        );</span><br><span class="line">        free (magicCookie);                                     <span class="comment">// 9</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;                                              <span class="comment">// 10</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>用于录制的音频队列。</li>
<li>录制到的音频文件。</li>
<li>结果变量，表达该函数是成功还是失败。</li>
<li>用于保存magic cookie数据大小的变量。</li>
<li>从音频队列获取magic cookie数据大小，并存储在<code>cookieSize</code>变量中。</li>
<li>分配一个字节数据来保存magic cookie信息。</li>
<li>通过查询音频队列的<code>kAudioQueueProperty_MagicCookie</code>属性获取magic cookie。</li>
<li>设置录制到的音频文件的magic cookie。<code>AudioFileSetProperty</code>函数在<code>AudioFile.h</code>头文件中声明。</li>
<li>释放临时magic cookie变量的内存。</li>
<li>返回该函数的成功或失败状态。</li>
</ol>
<h2 id="设置音频格式进行录制">设置音频格式进行录制</h2>
<p>本节介绍如何为音频队列设置音频数据格式。音频队列使用该格式记录到文件。</p>
<p>要设置音频数据格式，需要指定：</p>
<ul>
<li>音频数据格式类型（如线性PCM、AAC等）</li>
<li>采样率（如44.1 kHz）</li>
<li>音频通道数（如2，立体声）</li>
<li>位深（如16位）</li>
<li>每数据包帧数量（如线性PCM，每包一帧）</li>
<li>音频文件类型（如CAF、AIFF等）</li>
<li>文件类型所需的音频数据格式的详细信息</li>
</ul>
<p>清单2-8写死了用来录制的音频格式的每个属性值。在生产代码中，通常允许用户部分或全部指定音频格式。无论采用哪种方式，目标都是填充<code>AQRecorderState</code>自定义结构体的<code>mDataFormat</code>字段，参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW15">Define a Custom Structure to Manage State</a>中的自定义结构体。</p>
<p><strong>清单2-8</strong> 指定音频队列的音频数据格式</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">AQRecorderState aqData;                                       <span class="comment">// 1</span></span><br><span class="line"> </span><br><span class="line">aqData.mDataFormat.mFormatID         = kAudioFormatLinearPCM; <span class="comment">// 2</span></span><br><span class="line">aqData.mDataFormat.mSampleRate       = <span class="number">44100.0</span>;               <span class="comment">// 3</span></span><br><span class="line">aqData.mDataFormat.mChannelsPerFrame = <span class="number">2</span>;                     <span class="comment">// 4</span></span><br><span class="line">aqData.mDataFormat.mBitsPerChannel   = <span class="number">16</span>;                    <span class="comment">// 5</span></span><br><span class="line">aqData.mDataFormat.mBytesPerPacket   =                        <span class="comment">// 6</span></span><br><span class="line">   aqData.mDataFormat.mBytesPerFrame =</span><br><span class="line">      aqData.mDataFormat.mChannelsPerFrame * <span class="keyword">sizeof</span> (SInt16);</span><br><span class="line">aqData.mDataFormat.mFramesPerPacket  = <span class="number">1</span>;                     <span class="comment">// 7</span></span><br><span class="line"> </span><br><span class="line">AudioFileTypeID fileType             = kAudioFileAIFFType;    <span class="comment">// 8</span></span><br><span class="line">aqData.mDataFormat.mFormatFlags =                             <span class="comment">// 9</span></span><br><span class="line">    kLinearPCMFormatFlagIsBigEndian</span><br><span class="line">    | kLinearPCMFormatFlagIsSignedInteger</span><br><span class="line">    | kLinearPCMFormatFlagIsPacked;</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>创建<code>AQRecorderState</code>结构体实例。结构体的<code>mDataFormat</code>字段包含一个<code>AudioStreamBasicDescription</code>结构体。在<code>mDataFormat</code>字段中设置的值提供了音频队列的初始音频格式，这也是记录到文件的音频格式。在<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW9">清单2-10</a>中，你可以获得音频格式的完整规范，Core Audio根据格式类型和文件类型提供了相关规范。</li>
<li>把音频数据格式类型定义为线性PCM。有关可用数据格式的完整列表，可参阅_<a target="_blank" rel="noopener" href="https://developer.apple.com/documentation/coreaudio/core_audio_data_types">Core Audio Data Types Reference</a>_。</li>
<li>把采样率设为44.1 kHz。</li>
<li>通道数设为2。</li>
<li>每个通道位深设为16。</li>
<li>每个数据包字节数和每帧字节数设为4（即2个通道乘以每个样本2个字节）。</li>
<li>每个数据包帧数量设为1。</li>
<li>文件类型设为AIFF。参阅<code>AudioFile.h</code>头文件的类型，可获得可用类型的完整列表。可以指定任意已安装的解码器的文件类型，如<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AboutAudioQueues/AboutAudioQueues.html#//apple_ref/doc/uid/TP40005343-CH5-SW14">Using Codecs and Audio Data Formats</a>所述。</li>
<li>设置指定文件类型所需的格式标志。</li>
</ol>
<h2 id="创建一个录制音频队列">创建一个录制音频队列</h2>
<p>现在，在设置了录制黑白你函数和音频数据格式之后，创建和配置用于录制的音频队列。</p>
<h3 id="创建录制音频队列">创建录制音频队列</h3>
<p>清单2-9展示了如何创建录制音频队列。注意，<code>AudioQueueNewInput</code>函数使用在之前步骤中配置的回调函数、自定义结构体和音频数据格式。</p>
<p><strong>清单2-9</strong> 创建录制音频队列</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">AudioQueueNewInput (                              <span class="comment">// 1</span></span><br><span class="line">    &amp;aqData.mDataFormat,                          <span class="comment">// 2</span></span><br><span class="line">    HandleInputBuffer,                            <span class="comment">// 3</span></span><br><span class="line">    &amp;aqData,                                      <span class="comment">// 4</span></span><br><span class="line">    <span class="literal">NULL</span>,                                         <span class="comment">// 5</span></span><br><span class="line">    kCFRunLoopCommonModes,                        <span class="comment">// 6</span></span><br><span class="line">    <span class="number">0</span>,                                            <span class="comment">// 7</span></span><br><span class="line">    &amp;aqData.mQueue                                <span class="comment">// 8</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>AudioQueueNewInput</code>函数创建一个新的录制音频队列。</li>
<li>录制的音频数据格式。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW4">Set Up an Audio Format for Recording</a>。</li>
<li>于录制音频队列一起使用的回调函数。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW24">Write a Recording Audio Queue Callback</a>。</li>
<li>录制音频队列的自定义数据结构体。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW15">Define a Custom Structure to Manage State</a>。</li>
<li>调用回调函数的run loop。使用<code>NULL</code>指定默认行为，回调函数将在内部的音频队列中的线程执行。这时典型的用法，允许音频队列在程序的用户界面线程等待用户停止录制的同时进行录制。</li>
<li>run loop模式。通常使用<code>kCFRunLoopCommonModes</code>。</li>
<li>保留参数，必须为<code>0</code>。</li>
<li>在输出时，新分配的录制音频队列。</li>
</ol>
<h3 id="从音频队列获取完整的音频格式">从音频队列获取完整的音频格式</h3>
<p>当音频队列创建后（参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW25">Creating a Recording Audio Queue</a>），<code>AudioStreamBasicDescription</code>可能比你填写的更完整，尤其是压缩格式。要获取完整的格式描述，调用清单2-10的<code>AudioQueueGetProperty</code>函数。创建要录制到的音频文件时，需使用完整的音频格式（参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW26">Create an Audio File</a>）。</p>
<p><strong>清单2-10</strong> 从音频队列获取音频格式</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">UInt32</span> dataFormatSize = <span class="keyword">sizeof</span> (aqData.mDataFormat);       <span class="comment">// 1</span></span><br><span class="line"> </span><br><span class="line">AudioQueueGetProperty (                                    <span class="comment">// 2</span></span><br><span class="line">    aqData.mQueue,                                         <span class="comment">// 3</span></span><br><span class="line">    kAudioQueueProperty_StreamDescription,                 <span class="comment">// 4</span></span><br><span class="line">    <span class="comment">// in Mac OS X, instead use</span></span><br><span class="line">    <span class="comment">//    kAudioConverterCurrentInputStreamDescription</span></span><br><span class="line">    &amp;aqData.mDataFormat,                                   <span class="comment">// 5</span></span><br><span class="line">    &amp;dataFormatSize                                        <span class="comment">// 6</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>获取在查询音频队列有关其音频数据格式时要使用的预期属性值大小。</li>
<li><code>AudioQueueGetProperty</code>函数获取音频队列中指定属性的值。</li>
<li>用于获取音频数据格式的音频队列。</li>
<li>用于获取音频队列的数据格式值的属性ID。</li>
<li>在输出时，从音频队列获得的<code>AudioStreamBasicDescription</code>结构体形式的完整音频数据格式。</li>
<li>输入时，是<code>AudioStreamBasicDescription</code>的预期大小。输出时，是其实际大小。在录制程序中不需要使用该值。</li>
</ol>
<h2 id="创建音频文件">创建音频文件</h2>
<p>创建并配置音频队列后，将创建一个音频文件，把音频数据记录到音频文件中，如清单2-11所示。音频文件使用之前存储在音频队列的自定义结构体中的数据格式和文件格式规范。</p>
<p><strong>清单2-11</strong> 创建一个音频文件进行录制</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">CFURLRef</span> audioFileURL =</span><br><span class="line">    <span class="built_in">CFURLCreateFromFileSystemRepresentation</span> (            <span class="comment">// 1</span></span><br><span class="line">        <span class="literal">NULL</span>,                                            <span class="comment">// 2</span></span><br><span class="line">        (<span class="keyword">const</span> <span class="built_in">UInt8</span> *) filePath,                        <span class="comment">// 3</span></span><br><span class="line">        strlen (filePath),                               <span class="comment">// 4</span></span><br><span class="line">        <span class="literal">false</span>                                            <span class="comment">// 5</span></span><br><span class="line">    );</span><br><span class="line"> </span><br><span class="line">AudioFileCreateWithURL (                                 <span class="comment">// 6</span></span><br><span class="line">    audioFileURL,                                        <span class="comment">// 7</span></span><br><span class="line">    fileType,                                            <span class="comment">// 8</span></span><br><span class="line">    &amp;aqData.mDataFormat,                                 <span class="comment">// 9</span></span><br><span class="line">    kAudioFileFlags_EraseFile,                           <span class="comment">// 10</span></span><br><span class="line">    &amp;aqData.mAudioFile                                   <span class="comment">// 11</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>CFURLCreateFromFileSystemRepresentation</code>函数（在<code>CFURL.h</code>头文件中声明），创建一个CFURL对象，该对象表示要录制到其中的文件。</li>
<li>使用<code>NULL</code>或<code>kCFAllocatorDefault</code>，使用当前默认的内存分配器。</li>
<li>想要转换为CFURL的文件系统路径。在生产代码中，通常会从用户获取<code>filePath</code>值。</li>
<li>文件系统路径中的字节数。</li>
<li><code>false</code>值表示<code>filePath</code>代表文件，而不是目录。</li>
<li><code>AudioFileCreateWithURL</code>函数（来自<code>AudioFile.h</code>头文件），创建一个新的音频文件，或初始化一个现有文件。</li>
<li>用于创建新的音频文件或使用现在文件进行初始化的URL。该URL是从第一步<code>CFURLCreateFromFileSystemRepresentation</code>获得的。</li>
<li>新文件的文件类型。在本章的示例代码中，之前已通过<code>kAudioFileAIFFType</code>设置为AIFF类型。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW4">Set Up an Audio Format for Recording</a>。</li>
<li>将记录到文件中的音频数据格式，指定为<code>AudioStreamBasicDescription</code>结构体。在本章的示例代码中，也已在“<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW4">Set Up an Audio Format for Recording</a>”中进行了设置。</li>
<li>如果文件已存在，则删除该文件。</li>
<li>在输出时，音频文件对象（<code>AudioFileID</code>类型）表示要录制到的音频文件。</li>
</ol>
<h2 id="设置音频队列缓冲区大小">设置音频队列缓冲区大小</h2>
<p>在准备在录制是使用一组音频队列缓冲区之前，调用之前的<code>DeriveBufferSize</code>函数（参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW14">Write a Function to Derive Recording Audio Queue Buffer Size</a>）。可以把该大小分配给正在使用的录制音频队列，如清单2-12所示：</p>
<p><strong>清单2-12</strong> 设置音频队列缓冲区大小</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">DeriveBufferSize (                               <span class="comment">// 1</span></span><br><span class="line">    aqData.mQueue,                               <span class="comment">// 2</span></span><br><span class="line">    aqData.mDataFormat,                          <span class="comment">// 3</span></span><br><span class="line">    <span class="number">0.5</span>,                                         <span class="comment">// 4</span></span><br><span class="line">    &amp;aqData.bufferByteSize                       <span class="comment">// 5</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>DeriveBufferSize</code>函数（定义在<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW14">Write a Function to Derive Recording Audio Queue Buffer Size</a>），设置合适的音频队列缓冲区大小。</li>
<li>配置缓冲区大小的音频队列。</li>
<li>在录制的文件的音频数据格式。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW4">Set Up an Audio Format for Recording</a>。</li>
<li>每个音频队列缓冲区应保留的秒数。此处设置半秒是个不错的选择。</li>
<li>在输出时，每个音频队列缓冲区的大小（以字节为单位）。该值放在音频队列的自定义结构体中。</li>
</ol>
<h2 id="准备一组音频队列缓冲区">准备一组音频队列缓冲区</h2>
<p>现在，请求音频队列（在<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW2">Create a Recording Audio Queue</a>创建的）准备一组音频队列缓冲区。清单2-13展示了如何操作。</p>
<p><strong>清单2-13</strong> 准备一组音频队列缓冲区</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; kNumberBuffers; ++i) &#123;           <span class="comment">// 1</span></span><br><span class="line">    AudioQueueAllocateBuffer (                       <span class="comment">// 2</span></span><br><span class="line">        aqData.mQueue,                               <span class="comment">// 3</span></span><br><span class="line">        aqData.bufferByteSize,                       <span class="comment">// 4</span></span><br><span class="line">        &amp;aqData.mBuffers[i]                          <span class="comment">// 5</span></span><br><span class="line">    );</span><br><span class="line"> </span><br><span class="line">    AudioQueueEnqueueBuffer (                        <span class="comment">// 6</span></span><br><span class="line">        aqData.mQueue,                               <span class="comment">// 7</span></span><br><span class="line">        aqData.mBuffers[i],                          <span class="comment">// 8</span></span><br><span class="line">        <span class="number">0</span>,                                           <span class="comment">// 9</span></span><br><span class="line">        <span class="literal">NULL</span>                                         <span class="comment">// 10</span></span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>遍历分配和入队每个音频队列缓冲区。</li>
<li><code>AudioQueueAllocateBuffer</code>函数请求音频队列分配音频队列缓冲区。</li>
<li>执行分配并持有缓冲区的音频队列。</li>
<li>分配的新音频队列缓冲区的大小（以字节为单位）。参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQRecord/RecordingAudio.html#//apple_ref/doc/uid/TP40005343-CH4-SW14">Write a Function to Derive Recording Audio Queue Buffer Size</a>。</li>
<li>在输出时，是新分配的音频队列缓冲区。指向缓冲区的指针放在和音频队列一起使用的自定义结构体中。</li>
<li><code>AudioQueueEnqueueBuffer</code>函数把音频队列缓冲区添加到缓冲区队列的末尾。</li>
<li>向其添加缓冲区的缓冲区队列的音频队列。</li>
<li>正在入队的音频队列缓冲区。</li>
<li>缓冲区入队时未使用该参数。</li>
<li>缓冲区入队时未使用该参数。</li>
</ol>
<h2 id="录制音频">录制音频</h2>
<p>有了前面的代码，录制过程显得格外简单，如清单2-14所示。</p>
<p><strong>清单2-14</strong> 录制音频</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">aqData.mCurrentPacket = <span class="number">0</span>;                           <span class="comment">// 1</span></span><br><span class="line">aqData.mIsRunning = <span class="literal">true</span>;                            <span class="comment">// 2</span></span><br><span class="line"> </span><br><span class="line">AudioQueueStart (                                    <span class="comment">// 3</span></span><br><span class="line">    aqData.mQueue,                                   <span class="comment">// 4</span></span><br><span class="line">    <span class="literal">NULL</span>                                             <span class="comment">// 5</span></span><br><span class="line">);</span><br><span class="line"><span class="comment">// Wait, on user interface thread, until user stops the recording</span></span><br><span class="line">AudioQueueStop (                                     <span class="comment">// 6</span></span><br><span class="line">    aqData.mQueue,                                   <span class="comment">// 7</span></span><br><span class="line">    <span class="literal">true</span>                                             <span class="comment">// 8</span></span><br><span class="line">);</span><br><span class="line"> </span><br><span class="line">aqData.mIsRunning = <span class="literal">false</span>;                           <span class="comment">// 9</span></span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li>初始化数据包索引为<code>0</code>，在音频文件的开头开始录制。</li>
<li>在自定义结构体中设置标志，以指示音频队列正在运行。录制音频队列回调函数使用该标志。</li>
<li><code>AudioQueueStart</code>函数在其自己的线程上启动音频队列。</li>
<li>音频队列开始。</li>
<li>使用<code>NULL</code>表示音频队列应立即开始录制。</li>
<li><code>AudioQueueStop</code>函数停止并重置录制音频队列。</li>
<li>音频队列停止。</li>
<li>使用<code>true</code>来同步停止。有关同步和异步的说明，参阅<a target="_blank" rel="noopener" href="https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AboutAudioQueues/AboutAudioQueues.html#//apple_ref/doc/uid/TP40005343-CH5-SW17">Audio Queue Control and State</a>。</li>
<li>在自定义结构体中设置标志，以表示音频队列还没运行。</li>
</ol>
<h2 id="录制后清理">录制后清理</h2>
<p>完成录制后，需要处理音频队列并关闭音频文件，如清单2-15所示。</p>
<p><strong>清单2-15</strong> 录制后清理</p>
<figure class="highlight objectivec"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">AudioQueueDispose (                                 <span class="comment">// 1</span></span><br><span class="line">    aqData.mQueue,                                  <span class="comment">// 2</span></span><br><span class="line">    <span class="literal">true</span>                                            <span class="comment">// 3</span></span><br><span class="line">);</span><br><span class="line"> </span><br><span class="line">AudioFileClose (aqData.mAudioFile);                 <span class="comment">// 4</span></span><br></pre></td></tr></table></figure>
<p>下面是该代码的工作方式：</p>
<ol type="1">
<li><code>AudioQueueDispose</code>函数处理音频队列及其所有资源，包括缓冲区。</li>
<li>要处理的音频队列。</li>
<li>使用<code>true</code>来同步（即立即）处理音频队列。</li>
<li>关闭用于录制的音频文件。<code>AudioFileClose</code>函数在<code>AudioFile.h</code>头文件中声明。</li>
</ol>
<h2 id="总结">总结</h2>
<ul>
<li>使用录制功能一般步骤：
<ol type="1">
<li>定义自定义结构体来管理状态、格式以及路径信息等。</li>
<li>编写回调函数来执行实际的录制数据处理。</li>
<li>填充自定义结构体中的各个字段，包括录制到的文件的数据流、文件路径。</li>
<li>创建音频队列、音频队列缓冲区、要写入的文件。</li>
<li>通知音频队列开始录制。</li>
<li>录制完毕后，通知音频队列停止录制，然后释放。这同时会释放它所拥有的所有缓冲区。</li>
</ol></li>
<li>对于使用OC、Swift代码，可以直接把结构体的字段直接分散到类定义中。</li>
<li>录制回调函数任务：
<ul>
<li>把填充进音频队列缓冲区的内容写入到文件。</li>
<li>把写入文件的音频队列缓冲区排队到队列中。这样才可以接受更多数据。</li>
</ul></li>
<li>回调函数中的<code>const AudioStreamPacketDescription  *inPacketDesc</code>包含VBR包描述的数量（<code>mVariableFramesInPacket</code>），如果是0则表示这是CBR数据。该数据来自编码器。</li>
<li>每个音频队列缓冲区时长可以设置为0.5秒。</li>
</ul>

    </div>

    
    
    
        

  <div class="followme">
    <p>欢迎关注我的其它发布渠道</p>

    <div class="social-list">

        <div class="social-item">
          <a target="_blank" class="social-link" href="/atom.xml">
            <span class="icon">
              <i class="fa fa-rss"></i>
            </span>

            <span class="label">RSS</span>
          </a>
        </div>
    </div>
  </div>


      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/Apple/" rel="tag"># Apple</a>
              <a href="/tags/Audio-Queue-Services-Programming-Guide/" rel="tag"># Audio Queue Services Programming Guide</a>
              <a href="/tags/%E9%9F%B3%E8%A7%86%E9%A2%91/" rel="tag"># 音视频</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/posts/audio_queue_services_pg_about_audio_queues/" rel="prev" title="Audio Queue Services Programming Guide：关于音频队列">
      <i class="fa fa-chevron-left"></i> Audio Queue Services Programming Guide：关于音频队列
    </a></div>
      <div class="post-nav-item">
    <a href="/posts/audio_queue_services_pg_playing_audio/" rel="next" title="Audio Queue Services Programming Guide：播放音频">
      Audio Queue Services Programming Guide：播放音频 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          
    <div class="comments" id="gitalk-container"></div>

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%AE%9A%E4%B9%89%E4%B8%80%E4%B8%AA%E7%AE%A1%E7%90%86%E7%8A%B6%E6%80%81%E7%9A%84%E7%BB%93%E6%9E%84%E4%BD%93"><span class="nav-text">定义一个管理状态的结构体</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BC%96%E5%86%99%E7%94%A8%E4%BA%8E%E5%BD%95%E5%88%B6%E7%9A%84%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E7%9A%84%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0"><span class="nav-text">编写用于录制的音频队列的回调函数</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%94%A8%E4%BA%8E%E5%BD%95%E5%88%B6%E7%9A%84%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0%E7%9A%84%E5%A3%B0%E6%98%8E"><span class="nav-text">用于录制的音频队列回调函数的声明</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%B0%86%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E7%BC%93%E5%86%B2%E5%8C%BA%E4%B8%AD%E7%9A%84%E6%95%B0%E6%8D%AE%E5%86%99%E5%85%A5%E7%A3%81%E7%9B%98"><span class="nav-text">将音频队列缓冲区中的数据写入磁盘</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%8E%92%E9%98%9F%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E7%BC%93%E5%86%B2%E5%8C%BA"><span class="nav-text">排队音频队列缓冲区</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E9%9F%B3%E9%A2%91%E5%BD%95%E5%88%B6%E7%9A%84%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0"><span class="nav-text">一个完整的音频录制的音频队列回调函数</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BC%96%E5%86%99%E5%87%BD%E6%95%B0%E8%AE%A1%E7%AE%97%E7%94%A8%E4%BA%8E%E5%BD%95%E5%88%B6%E7%9A%84%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E7%BC%93%E5%86%B2%E5%8C%BA%E5%A4%A7%E5%B0%8F"><span class="nav-text">编写函数计算用于录制的音频队列缓冲区大小</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%AE%BE%E7%BD%AE%E9%9F%B3%E9%A2%91%E6%96%87%E4%BB%B6magic-cookie"><span class="nav-text">设置音频文件Magic Cookie</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%AE%BE%E7%BD%AE%E9%9F%B3%E9%A2%91%E6%A0%BC%E5%BC%8F%E8%BF%9B%E8%A1%8C%E5%BD%95%E5%88%B6"><span class="nav-text">设置音频格式进行录制</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E5%BD%95%E5%88%B6%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97"><span class="nav-text">创建一个录制音频队列</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%88%9B%E5%BB%BA%E5%BD%95%E5%88%B6%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97"><span class="nav-text">创建录制音频队列</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BB%8E%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E8%8E%B7%E5%8F%96%E5%AE%8C%E6%95%B4%E7%9A%84%E9%9F%B3%E9%A2%91%E6%A0%BC%E5%BC%8F"><span class="nav-text">从音频队列获取完整的音频格式</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%88%9B%E5%BB%BA%E9%9F%B3%E9%A2%91%E6%96%87%E4%BB%B6"><span class="nav-text">创建音频文件</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%AE%BE%E7%BD%AE%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E7%BC%93%E5%86%B2%E5%8C%BA%E5%A4%A7%E5%B0%8F"><span class="nav-text">设置音频队列缓冲区大小</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%87%86%E5%A4%87%E4%B8%80%E7%BB%84%E9%9F%B3%E9%A2%91%E9%98%9F%E5%88%97%E7%BC%93%E5%86%B2%E5%8C%BA"><span class="nav-text">准备一组音频队列缓冲区</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%BD%95%E5%88%B6%E9%9F%B3%E9%A2%91"><span class="nav-text">录制音频</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%BD%95%E5%88%B6%E5%90%8E%E6%B8%85%E7%90%86"><span class="nav-text">录制后清理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%80%BB%E7%BB%93"><span class="nav-text">总结</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="權咚"
      src="/images/icon.png">
  <p class="site-author-name" itemprop="name">權咚</p>
  <div class="site-description" itemprop="description">🤘🤯👾</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">84</span>
          <span class="site-state-item-name">文章</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">6</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">10</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/bqlin" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;bqlin" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="https://weibo.com/hearingdog" title="Weibo → https:&#x2F;&#x2F;weibo.com&#x2F;hearingdog" rel="noopener" target="_blank"><i class="fab fa-weibo fa-fw"></i>Weibo</a>
      </span>
  </div>


  <div class="links-of-blogroll motion-element">
    <div class="links-of-blogroll-title"><i class="fa fa-link fa-fw"></i>
      Links
    </div>
    <ul class="links-of-blogroll-list">
        <li class="links-of-blogroll-item">
          <a href="https://xiaozhuanlan.com/metal-reference-translation" title="https:&#x2F;&#x2F;xiaozhuanlan.com&#x2F;metal-reference-translation" rel="noopener" target="_blank">Metal内参</a>
        </li>
        <li class="links-of-blogroll-item">
          <a href="https://xiaozhuanlan.com/wwdc21" title="https:&#x2F;&#x2F;xiaozhuanlan.com&#x2F;wwdc21" rel="noopener" target="_blank">WWDC21 内参</a>
        </li>
    </ul>
  </div>

      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 2016 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">權咚</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-chart-area"></i>
    </span>
    <span title="站点总字数">279k</span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-coffee"></i>
    </span>
    <span title="站点阅读时长">4:14</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Gemini</a> 强力驱动
  </div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/pangu@4/dist/browser/pangu.min.js"></script>

<script src="/js/utils.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  
  <script>
    (function(){
      var canonicalURL, curProtocol;
      //Get the <link> tag
      var x=document.getElementsByTagName("link");
		//Find the last canonical URL
		if(x.length > 0){
			for (i=0;i<x.length;i++){
				if(x[i].rel.toLowerCase() == 'canonical' && x[i].href){
					canonicalURL=x[i].href;
				}
			}
		}
    //Get protocol
	    if (!canonicalURL){
	    	curProtocol = window.location.protocol.split(':')[0];
	    }
	    else{
	    	curProtocol = canonicalURL.split(':')[0];
	    }
      //Get current URL if the canonical URL does not exist
	    if (!canonicalURL) canonicalURL = window.location.href;
	    //Assign script content. Replace current URL with the canonical URL
      !function(){var e=/([http|https]:\/\/[a-zA-Z0-9\_\.]+\.baidu\.com)/gi,r=canonicalURL,t=document.referrer;if(!e.test(r)){var n=(String(curProtocol).toLowerCase() === 'https')?"https://sp0.baidu.com/9_Q4simg2RQJ8t7jm9iCKT-xh_/s.gif":"//api.share.baidu.com/s.gif";t?(n+="?r="+encodeURIComponent(document.referrer),r&&(n+="&l="+r)):r&&(n+="?l="+r);var i=new Image;i.src=n}}(window);})();
  </script>




  
<script src="/js/local-search.js"></script>











<script>
if (document.querySelectorAll('pre.mermaid').length) {
  NexT.utils.getScript('//cdn.jsdelivr.net/npm/mermaid@8/dist/mermaid.min.js', () => {
    mermaid.initialize({
      theme    : 'default',
      logLevel : 3,
      flowchart: { curve     : 'linear' },
      gantt    : { axisFormat: '%m/%d/%Y' },
      sequence : { actorMargin: 50 }
    });
  }, window.mermaid);
}
</script>


  

  

  

<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.css">

<script>
NexT.utils.loadComments(document.querySelector('#gitalk-container'), () => {
  NexT.utils.getScript('//cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js', () => {
    var gitalk = new Gitalk({
      clientID    : '7ed324dbfd2741c35c0e',
      clientSecret: '75a59e1f6a55202c4a1b0ec3570ad5b5dccbc390',
      repo        : 'bqlin.github.io',
      owner       : 'bqlin',
      admin       : ['bqlin'],
      id          : '13a9ca4f03701ba2dfdd429f74bf73f5',
        language: 'zh-CN',
      distractionFreeMode: true
    });
    gitalk.render('gitalk-container');
  }, window.Gitalk);
});
</script>

<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","tagMode":false,"debug":false,"model":{"scale":1,"jsonPath":"/live2dw/assets/hijiki.model.json"},"display":{"position":"right","vOffset":-100},"mobile":{"show":false,"scale":0.5},"log":false});</script></body>
</html>
